/*
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 *
 *  Copyright (C) 2006-2008 XNeur Team
 *
 */

#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <stdio.h>
#include <signal.h>
#include <unistd.h>

#include "xnconfig_files.h"
#include "xnconfig_memory.h"

#include "types.h"
#include "utils.h"
#include "list_char.h"
#include "log.h"

#include "xnconfig.h"

#define LIBRARY_VERSION_MAJOR		9
#define LIBRARY_VERSION_MINOR		4
#define OPTIONS_DELIMETER		" "

static const char *log_levels[] =	{"Error", "Warning", "Log", "Debug", "Trace"};
static const char *bool_names[] =	{"No", "Yes"};
static const char *fix_names[]  =	{"Fixed"};
static const char *modifier_names[] =	{"Shift", "Control", "Alt", "Super"};

static const char *option_names[] = 	{
						"ExcludeApp", "AddBind", "LogLevel", "AddLanguage", "Reserved1",
						"Reserved2", "GrabMouse", "Version", "SaveSelectionMode",
						"SendDelay", "SaveLog",
						"FlushBufferWhenPressEnter", "DontProcessWhenPressEnter", "AddAction"
					};
static const char *action_names[] =	{
						"ChangeWord", "ChangeString", "ChangeMode",
						"ChangeSelected", "TranslitSelected", "ChangecaseSelected",
						"EnableLayout1", "EnableLayout2", "EnableLayout3", "EnableLayout4"
					};

pid_t getsid(pid_t pid);
//char* strsep(char **stringp, const char *delim);

static char* get_word(char **string)
{
	return strsep(string, OPTIONS_DELIMETER);
}

#define get_option_index(options, option) \
	get_option_index_size(options, option, sizeof(options) / sizeof(options[0]));

static int get_option_index_size(const char *options[], char *option, int options_count)
{
	for (int i = 0; i < options_count; i++)
	{
		if (strcmp(option, options[i]) == 0)
			return i;
	}
	return -1;
}

static void xkeylog_config_get_library_version(int *major_version, int *minor_version)
{
	*major_version = LIBRARY_VERSION_MAJOR;
	*minor_version = LIBRARY_VERSION_MINOR;
}

static const char* xkeylog_config_get_bool_name(int option)
{
	return bool_names[option];
}

static void parse_line(struct _xkeylog_config *p, char *line)
{
	if (line[0] == '#')
		return;

	char *option = get_word(&line);

	int index = get_option_index(option_names, option);
	if (index == -1)
	{
		log_message(WARNING, _("Unrecognized option \"%s\" detected"), option);
		return;
	}

	char *full_string = strdup(line);

	char *param = get_word(&line);
	if (param == NULL)
	{
		free(full_string);
		log_message(WARNING, _("Param mismatch for option %s"), option);
		return;
	}

	switch (index)
	{
		case 0: // Get Applications Names
		{
			p->excluded_apps->add(p->excluded_apps, full_string);
			break;
		}
		case 1: // Get Keyboard Binds
		{
			int action = get_option_index(action_names, param);
			if (action == -1)
			{
				log_message(WARNING, _("Invalid value for action name specified"));
				break;
			}

			while (TRUE)
			{
				char *modifier = get_word(&line);
				if (modifier == NULL)
					break;

				if (modifier[0] == '\0')
					continue;

				int index = get_option_index(modifier_names, modifier);
				if (index == -1)
					p->hotkeys[action].key = strdup(modifier);
				else
					p->hotkeys[action].modifiers |= (1 << index);
			}

			break;
		}
		case 2: // Get Log Level
		{
			int index = get_option_index(log_levels, param);
			if (index == -1)
			{
				log_message(WARNING, _("Invalid value for log level specified"));
				break;
			}

			p->log_level = index;
			log_set_level(p->log_level);
			break;
		}
		case 3: // Add Language
		{
			char *dir	= get_word(&line);
			char *group	= get_word(&line);
			char *fixed = get_word(&line);
			
			if (dir == NULL || group == NULL)
			{
				log_message(ERROR, _("Argument number mismatch for AddLanguage option"));
				break;
			}

			int fix_index = FALSE;
			if (fixed != NULL)
			{
				int index = get_option_index(fix_names, (char *)fixed);
				if (index == 0) // Fixed
					fix_index = TRUE;
			}
			
			p->add_language(p, param, dir, atoi(group), fix_index);
			break;
		}
		case 4: // Reserved1
		{
			break;
		}
		case 5: // Reserved2
		{
			break;
		}
		case 6: // Get Mouse Grab Mode
		{
			int index = get_option_index(bool_names, param);
			if (index == -1)
			{
				log_message(WARNING, _("Invalid value for mouse grab mode specified"));
				break;
			}

			p->grab_mouse = index;
			break;
		}
		case 7: // Get config version
		{
			p->version = strdup(param);
			break;
		}
		case 8: // Get Save Selection Mode
		{
			int index = get_option_index(bool_names, param);
			if (index == -1)
			{
				log_message(WARNING, _("Invalid value for save selection mode specified"));
				break;
			}

			p->save_selection = index;
			break;
		}
		case 9: // Backevent Delay
		{
			p->send_delay = atoi(param);
			if (p->send_delay < 0 || p->send_delay > 50)
			{
				log_message(WARNING, _("Send delay must be between 0 and 50"));
				p->send_delay = 0;
			}
			break;
		}
		case 10: // Save Keyboard Log
		{
			int index = get_option_index(bool_names, param);
			if (index == -1)
			{
				log_message(WARNING, _("Invalid value for save keyboard log mode specified"));
				break;
			}

			p->save_keyboard_log = index;
			break;
		}
		case 11: // Flush internal buffer when pressed Enter Mode
		{
			int index = get_option_index(bool_names, param);
			if (index == -1)
			{
				log_message(WARNING, _("Invalid value for flush internal buffer when pressed Enter mode specified"));
				break;
			}

			p->flush_buffer_when_press_enter = index;
			break;
		}
		case 12: // Don't process word when pressed Enter Mode
		{
			int index = get_option_index(bool_names, param);
			if (index == -1)
			{
				log_message(WARNING, _("Invalid value for don't processing word when pressed Enter mode specified"));
				break;
			}

			p->dont_process_when_press_enter = index;
			break;
		}
		case 13: // User actions
		{
			int action = p->actions->action_command->data_count;
			p->actions->action_hotkey =  (struct _xkeylog_hotkey *) realloc(p->actions->action_hotkey, (action + 1) * sizeof(struct _xkeylog_hotkey));
			p->actions->action_hotkey[action].key = NULL;
			p->actions->action_hotkey[action].modifiers = 0;
			
			while (TRUE)
			{
				
				if (param == NULL)
					break;

				if (param[0] == '\0')
					continue;
				
				int index = get_option_index(modifier_names, param);

				if (index == -1)
				{
					if (param != NULL)
						p->actions->action_hotkey[action].key = strdup(param);
					if (line != NULL)
						p->actions->action_command->add_last(p->actions->action_command, line);
					break;
				}	
				else
					p->actions->action_hotkey[action].modifiers |= (1 << index);

				param = get_word(&line);
			}
			break;
		}
	}
	free(full_string);
}

static int parse_config_file(struct _xkeylog_config *p, const char *dir_name, const char *file_name)
{
	struct _list_char *list = load_list(dir_name, file_name, FALSE);
	if (list == NULL)
	{
		log_message(ERROR, _("Can't find config file %s"), file_name);
		return FALSE;
	}

	for (int i = 0; i < list->data_count; i++)
		parse_line(p, list->data[i].string);

	list->uninit(list);
	return TRUE;
}

static int check_memory_attached(struct _xkeylog_config *p)
{
	if (p->xkeylog_data != NULL)
		return TRUE;

	p->xkeylog_data = (struct _xkeylog_data *) attach_memory_segment(sizeof(struct _xkeylog_data));
	if (p->xkeylog_data == NULL)
		return FALSE;

	return TRUE;
}

static void free_structures(struct _xkeylog_config *p)
{
	int total_user_actions = p->actions->action_command->data_count;
	
	p->window_layouts->uninit(p->window_layouts);
	p->manual_apps->uninit(p->manual_apps);
	p->auto_apps->uninit(p->auto_apps);
	p->layout_remember_apps->uninit(p->layout_remember_apps);
	p->excluded_apps->uninit(p->excluded_apps);
	p->actions->action_command->uninit(p->actions->action_command);
	
	if (p->version != NULL)
	{
		free(p->version);
		p->version = NULL;
	}

	for (int lang = 0; lang < p->total_languages; lang++)
	{
		free(p->languages[lang].name);
		free(p->languages[lang].dir);
	}

	p->total_languages = 0;

	if (p->languages != NULL)
	{
		free(p->languages);
		p->languages = NULL;
	}

	for (int user_action = 0; user_action < total_user_actions; user_action++)
	{
		if (p->actions->action_hotkey[user_action].key != NULL)
			free(p->actions->action_hotkey[user_action].key);
	}
		
	for (int action = 0; action < MAX_HOTKEYS; action++)
	{
		if (p->hotkeys[action].key != NULL)
			free(p->hotkeys[action].key);
	}

	bzero(p->actions->action_hotkey, total_user_actions * sizeof(struct _xkeylog_hotkey));
	bzero(p->hotkeys, MAX_HOTKEYS * sizeof(struct _xkeylog_hotkey));
}

static void xkeylog_config_reload(struct _xkeylog_config *p)
{
	int process_id = p->xkeylog_data->process_id;
	if (process_id <= 0)
		return;

	kill(process_id, SIGHUP);
}

static void xkeylog_config_set_pid(struct _xkeylog_config *p, int process_id)
{
	p->xkeylog_data->process_id = process_id;
}

static int xkeylog_config_kill(struct _xkeylog_config *p)
{
	int process_id = p->xkeylog_data->process_id;
	if (process_id <= 0)
		return FALSE;

	if (kill(process_id, SIGTERM) == -1)
		return FALSE;

	xkeylog_config_set_pid(p, 0);

	return TRUE;
}

static int xkeylog_config_get_pid(struct _xkeylog_config *p)
{
	int process_id = p->xkeylog_data->process_id;
	if (process_id <= 0)
		return -1;

	if (getsid(process_id) == -1)
		return -1;

	return p->xkeylog_data->process_id;
}

static int xkeylog_config_load(struct _xkeylog_config *p)
{
	if (!parse_config_file(p, NULL, CONFIG_NAME))
		return FALSE;

	return TRUE;
}

static void xkeylog_config_clear(struct _xkeylog_config *p)
{
	free_structures(p);

	p->window_layouts	= list_char_init();
	p->manual_apps		= list_char_init();
	p->auto_apps		= list_char_init();
	p->layout_remember_apps	= list_char_init();
	p->excluded_apps	= list_char_init();
	p->actions->action_command = list_char_init();
}

static int xkeylog_config_save(struct _xkeylog_config *p)
{
 	char *config_file_path_name = get_home_file_path_name(NULL, CONFIG_NAME);

	log_message(LOG, _("Saving main config to %s"), config_file_path_name);

	FILE *stream = fopen(config_file_path_name, "w");
	if (stream == NULL)
	{
		log_message(ERROR, _("Can't create file %s"), config_file_path_name);
		free(config_file_path_name);
		return FALSE;
	}

	free(config_file_path_name);

	fprintf(stream, "# It's a X Neural Switcher configuration file by XNeur\n# All values writted XNeur\n\n");

	fprintf(stream, "# Config version\nVersion %s\n\n", VERSION);

	fprintf(stream, "# Level of messages program will write to output\n");
	fprintf(stream, "#LogLevel Error\n");
	fprintf(stream, "#LogLevel Warning\n");
	fprintf(stream, "#LogLevel Log\n");
	fprintf(stream, "#LogLevel Debug\n");
	fprintf(stream, "#LogLevel Trace\n");
	fprintf(stream, "LogLevel %s\n\n", p->get_log_level_name(p));

	fprintf(stream, "# Define used languages\n");
	fprintf(stream, "# See Settings page on http://www.xkeylog.ru for details\n");

	for (int lang = 0; lang < p->total_languages; lang++)
	{
		fprintf(stream, "AddLanguage %s %s %d ", p->languages[lang].name, p->languages[lang].dir, p->languages[lang].group);
		if (p->languages[lang].fixed)
			fprintf(stream, "%s\n", fix_names[0]);
		else
			fprintf(stream, "\n");
	}
	fprintf(stream, "\n");

	fprintf(stream, "# Add Applications names to exclude it from procces with xkeylog\n");
	fprintf(stream, "# Xneur will not process the input for this applications\n");
	fprintf(stream, "# Example:\n");
	fprintf(stream, "#ExcludeApp Gaim\n");
	for (int i = 0; i < p->excluded_apps->data_count; i++)
		fprintf(stream, "ExcludeApp %s\n", p->excluded_apps->data[i].string);
	fprintf(stream, "\n");

	fprintf(stream, "# Use this parameter to force set work mode in current application to Auto.\n");
	fprintf(stream, "# Example:\n");
	fprintf(stream, "#SetAutoApp Gedit\n");
	for (int i = 0; i < p->auto_apps->data_count; i++)
		fprintf(stream, "SetAutoApp %s\n", p->auto_apps->data[i].string);
	fprintf(stream, "\n");

	fprintf(stream, "# Use this parameter to force set work mode in current application to Manual.\n");
	fprintf(stream, "# Example:\n");
	fprintf(stream, "#SetManualApp Anjuta\n");
	for (int i = 0; i < p->manual_apps->data_count; i++)
		fprintf(stream, "SetManualApp %s\n", p->manual_apps->data[i].string);
	fprintf(stream, "\n");

	fprintf(stream, "# Binds hotkeys for some actions\n");
	for (int action = 0; action < MAX_HOTKEYS; action++)
	{
		fprintf(stream, "AddBind %s ", action_names[action]);

		const int total_modifiers = sizeof(modifier_names) / sizeof(modifier_names[0]);
		for (int i = 0; i < total_modifiers; i++)
		{
			if (p->hotkeys[action].modifiers & (1 << i))
				fprintf(stream, "%s ", modifier_names[i]);
		}

		if (p->hotkeys[action].key != NULL)
			fprintf(stream, "%s", p->hotkeys[action].key);
		fprintf(stream, "\n");
	}
	fprintf(stream, "\n");

	fprintf(stream, "# This option add user action when pressed key bind\n");
	fprintf(stream, "# Example:\n");
	fprintf(stream, "#AddAction Control Alt f firefox\n");
	for (int action = 0; action < p->actions->action_command->data_count; action++)
	{
		fprintf(stream, "AddAction ");

		const int total_modifiers = sizeof(modifier_names) / sizeof(modifier_names[0]);
		for (int i = 0; i < total_modifiers; i++)
		{
			if (p->actions->action_hotkey[action].modifiers & (1 << i))
				fprintf(stream, "%s ", modifier_names[i]);
		}

		fprintf(stream, "%s %s\n", p->actions->action_hotkey[action].key
								, p->actions->action_command->data[action].string);
	}
	fprintf(stream, "\n");
			
	fprintf(stream, "# This option enable or disable mouse processing\n");
	fprintf(stream, "# Example:\n");
	fprintf(stream, "#GrabMouse Yes\n");
	fprintf(stream, "GrabMouse %s\n\n", p->get_bool_name(p->grab_mouse));

	fprintf(stream, "# This option enable or disable saving selection text\n");
	fprintf(stream, "# Example:\n");
	fprintf(stream, "#SaveSelectionMode No\n");
	fprintf(stream, "SaveSelectionMode %s\n\n", p->get_bool_name(p->save_selection));

	fprintf(stream, "# This option define delay before sendind events to application (in milliseconds between 0 to 50).\n");
	fprintf(stream, "SendDelay %d\n\n", p->send_delay);

	fprintf(stream, "# This option enable or disable logging keyboard\n");
	fprintf(stream, "# Example:\n");
	fprintf(stream, "#SaveLog No\n");
	fprintf(stream, "SaveLog %s\n\n", p->get_bool_name(p->save_keyboard_log));

	fprintf(stream, "# This option enable or disable correction of iNCIDENTAL CapsLock\n");
	fprintf(stream, "# Example:\n");
	fprintf(stream, "#CorrectIncidentalCaps Yes\n");
	fprintf(stream, "CorrectIncidentalCaps %s\n\n", p->get_bool_name(p->correct_incidental_caps));

	fprintf(stream, "# This option enable or disable correction of two CApital letter\n");
	fprintf(stream, "# Example:\n");
	fprintf(stream, "#CorrectTwoCapitalLetter Yes\n");
	fprintf(stream, "CorrectTwoCapitalLetter %s\n\n", p->get_bool_name(p->correct_two_capital_letter));
			
	fprintf(stream, "# This option enable or disable flushing internal buffer when pressed Enter or Tab\n");
	fprintf(stream, "# Example:\n");
	fprintf(stream, "#FlushBufferWhenPressEnter Yes\n");
	fprintf(stream, "FlushBufferWhenPressEnter %s\n\n", p->get_bool_name(p->flush_buffer_when_press_enter));

	fprintf(stream, "# This option disable or enable processing word when pressed Enter or Tab\n");
	fprintf(stream, "# Example:\n");
	fprintf(stream, "#DontProcessWhenPressEnter Yes\n");
	fprintf(stream, "DontProcessWhenPressEnter %s\n\n", p->get_bool_name(p->dont_process_when_press_enter));

	fprintf(stream, "\n");

	fprintf(stream, "# That's all\n");

	fclose(stream);

	return TRUE;
}

static int xkeylog_config_replace(struct _xkeylog_config *p)
{
	char *config_file_path_name		= get_home_file_path_name(NULL, CONFIG_NAME);
	char *config_backup_file_path_name	= get_home_file_path_name(NULL, CONFIG_BCK_NAME);

	log_message(LOG, _("Moving config file from %s to %s"), config_file_path_name, config_backup_file_path_name);

	remove(config_backup_file_path_name);

	if (rename(config_file_path_name, config_backup_file_path_name) != 0)
	{
		log_message(ERROR, _("Can't move file!"), config_backup_file_path_name);

		free(config_file_path_name);
		free(config_backup_file_path_name);
		return FALSE;
	}

	free(config_file_path_name);
	free(config_backup_file_path_name);

	return p->load(p);
}

static char* xkeylog_config_get_lang_name(struct _xkeylog_config *p, int lang)
{
	if (lang < 0 || lang >= p->total_languages)
		return NULL;
	return p->languages[lang].name;
}

static int xkeylog_config_get_lang_group(struct _xkeylog_config *p, int lang)
{
	if (lang < 0 || lang >= p->total_languages)
		return -1;
	return p->languages[lang].group;
}

static int xkeylog_config_find_group_lang(struct _xkeylog_config *p, int group)
{
	for (int lang = 0; lang < p->total_languages; lang++)
	{
		if (p->languages[lang].group == group)
			return lang;
	}
	return -1;
}

static void xkeylog_config_add_language(struct _xkeylog_config *p, const char *name, const char *dir, int group, int fixed)
{
	if (name == NULL || dir == NULL)
	{
		log_message(ERROR, _("Can't add language with empty name or dir"));
		return;
	}

	p->languages = (struct _xkeylog_language *) realloc(p->languages, (p->total_languages + 1) * sizeof(struct _xkeylog_language));
	bzero(&(p->languages[p->total_languages]), sizeof(struct _xkeylog_language));

	p->languages[p->total_languages].name	= strdup(name);
	p->languages[p->total_languages].dir	= strdup(dir);
	p->languages[p->total_languages].group	= group;
	p->languages[p->total_languages].fixed	= fixed;
	
	p->total_languages++;
}

static const char* xkeylog_config_get_log_level_name(struct _xkeylog_config *p)
{
	return log_levels[p->log_level];
}

static void xkeylog_config_uninit(struct _xkeylog_config *p)
{
	free_structures(p);
	
	free(p->hotkeys);
	
	free(p->actions);
	
	free(p);
}

struct _xkeylog_config* xkeylog_config_init(void)
{
	struct _xkeylog_config *p = (struct _xkeylog_config *) malloc(sizeof(struct _xkeylog_config));
	bzero(p, sizeof(struct _xkeylog_config));

	if (!check_memory_attached(p))
	{
		free(p);
		return NULL;
	}

	p->hotkeys = (struct _xkeylog_hotkey *) malloc(MAX_HOTKEYS * sizeof(struct _xkeylog_hotkey));
	bzero(p->hotkeys, MAX_HOTKEYS * sizeof(struct _xkeylog_hotkey));

	p->log_level			= LOG;
	p->excluded_apps		= list_char_init();
	p->auto_apps			= list_char_init();
	p->manual_apps			= list_char_init();
	p->layout_remember_apps		= list_char_init();
	p->window_layouts		= list_char_init();

	p->actions = (struct _xkeylog_action *) malloc(sizeof(struct _xkeylog_action));
	p->actions->action_hotkey = (struct _xkeylog_hotkey *) malloc(sizeof(struct _xkeylog_hotkey));
	bzero(p->actions->action_hotkey, sizeof(struct _xkeylog_hotkey));
	p->actions->action_command	= list_char_init();
	
	// Function mapping

	p->get_library_version		= xkeylog_config_get_library_version;
	p->get_bool_name		= xkeylog_config_get_bool_name;

	p->load				= xkeylog_config_load;
	p->clear			= xkeylog_config_clear;
	p->save				= xkeylog_config_save;
	p->replace			= xkeylog_config_replace;
	p->reload			= xkeylog_config_reload;
	p->kill				= xkeylog_config_kill;
	p->set_pid			= xkeylog_config_set_pid;
	p->get_pid			= xkeylog_config_get_pid;
	p->get_lang_name		= xkeylog_config_get_lang_name;
	p->get_lang_group		= xkeylog_config_get_lang_group;
	p->find_group_lang		= xkeylog_config_find_group_lang;
	p->add_language			= xkeylog_config_add_language;
	p->get_log_level_name		= xkeylog_config_get_log_level_name;

	p->uninit			= xkeylog_config_uninit;

	return p;
}
